// Copyright 2013 The Go Circuit Project
// Use of this source code is governed by the license for
// The Go Circuit Project, found in the LICENSE file.
//
// Authors:
//   2013 Petar Maymounkov <p@gocircuit.org>

package ns

import (
	"fmt"
	"io"
	"math/rand"
	"reflect"
	"sync"
	"unsafe"

	"circuit/kit/rh"
)

// callFID
type callFID struct {
	rh.AnchoredFID
	//
	proc *interfaceProc
	mv   reflect.Value
	qid  rh.Q
	//
	io struct {
		UnregulatedFID
		io.Reader
		io.WriteCloser
	}
	o struct {
		sync.Mutex
		open bool
	}
}

func newCallFID(p *interfaceProc, mv reflect.Value) *callFID {
	qid := p.Q()
	qid.ID ^= uint64(rand.Int63()) // Q of every call instance must be different.
	fid := &callFID{
		proc: p,
		mv:   mv,
		qid:  qid,
	}
	return fid
}

func (fid *callFID) String() string {
	return fmt.Sprintf("%s:(%02x)", fid.proc.FName, uint64(uintptr(unsafe.Pointer(fid)))&0xff)
}

func (fid *callFID) Q() rh.Q {
	return fid.qid
}

func (fid *callFID) Open(flag rh.Flag, _ rh.Intr) (err error) {
	//log.Printf("(%p) callFID.Open “%s”: %v", fid, fid.proc.FName, flag)
	fid.o.Lock()
	defer fid.o.Unlock()
	if fid.o.open {
		return rh.ErrBusy
	}
	fid.o.open = true
	fid.proc.hit()
	fid.call(flag.Attr == rh.ReadOnly)
	//log.Printf("(%p) callFID.Open “%s”: %v -> ok", fid, fid.proc.FName, flag)
	return nil
}

func (fid *callFID) wasOpened() bool {
	fid.o.Lock()
	defer fid.o.Unlock()
	return fid.o.open
}

func (fid *callFID) call(readOnly bool) {
	if readOnly {
		fid.io.UnregulatedFID, fid.io.WriteCloser = NewPipeReaderFID(fid.proc.FName)
	} else {
		fid.io.UnregulatedFID, fid.io.Reader = NewPipeWriterFID(fid.proc.FName)
	}
	q, p := &RequestReader{fid.io.Reader}, &ResponseWriter{fid.io.WriteCloser}
	//
	go func() {
		defer func() {
			recover()
			p.Close()
		}()
		fid.mv.Call([]reflect.Value{reflect.ValueOf(q), reflect.ValueOf(p)})
	}()
}

func (fid *callFID) Clunk() {
	fid.io.Clunk()
}

func (fid *callFID) Stat() (*rh.Dir, error) {
	return fid.proc.Stat() // always returns length 0
}

// Walk returns a new unopened interfaceCallID for this proc
func (fid *callFID) Walk(wname []string) (rh.FID, error) {
	if len(wname) == 0 {
		return newCallFID(fid.proc, fid.mv), nil
	}
	return nil, rh.ErrNotExist
}

func (fid *callFID) Read(offset int64, count int, intr rh.Intr) (rh.Chunk, error) {
	if !fid.wasOpened() {
		return 0, rh.ErrClash
	}
	return fid.io.UnregulatedFID.Read(offset, count, intr)
}

func (fid *callFID) Write(offset int64, chunk rh.Chunk, intr rh.Intr) (n int, err error) {
	if !fid.wasOpened() {
		return 0, rh.ErrClash
	}
	return fid.io.UnregulatedFID.Write(offset, chunk, intr)
}
